/**
 * Copyright 2010, Lars J. Nilsson <http://www.larsan.net> 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.larsan.dconf.cli;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.prefs.InvalidPreferencesFormatException;
import java.util.prefs.Preferences;

import net.larsan.dconf.DNodeStore;
import net.larsan.dconf.DPath;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;

import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class ConnectorImpl extends AbstractCommand implements Connector {

	@Inject
	private DNodeStore store;
	
	private DPath currentPath;
	private boolean forceExit;
	
	@Inject
	private InputHandler input;
	
	@Inject
	private OutputHandler output;
	
	@Inject
	private CommandRegistry reg;
	
	private List<ConnectorListener> listeners = new CopyOnWriteArrayList<ConnectorListener>();
	
	public ConnectorImpl() {
		super(new String[0], "");
	}
	
	@Override
	public InputHandler getInputHandler() {
		return input;
	}
	
	@Override
	public OutputHandler getOutputHandler() {
		return output;
	}
	
	@Override
	protected void doExec(CommandLine cl) {
		System.out.println("MMMMM");
		if(!checkSetInitPath(cl)) return;
		if(cl.hasOption("e")) {
			handleExec(cl);
		} else if(cl.hasOption("i")) {
			handleImport(cl);
		} else {
			System.out.println("LLLL");
			lineStart();
			LineReader sc = input.getDefaultReader();
			while(sc.hasNextLine()) {
				System.out.println("HHHH");
				String line = sc.readLine();
				System.out.println("OOOOO");
				handleCommandLine(line);
				if(forceExit) {
					break;
				} else {
					lineStart();
				}
			}
		}
	}

	@Override
	protected void prepare(Options o) {
		o.addOption("e", "execute", true, "Execute command, non-interactive");
		o.addOption("i", "import", false, "Import data from application input stream, use -u for URL input");
		o.addOption("u", true, "Set a URL to import from, must be valid");
		o.addOption("f", true, "Set a File to import from, acts as a shorthand for the URL import");
		o.addOption("p", "path", true, "Initial path");
	}
	
	@Override
	public void markExit() {
		forceExit = true;
	}
	
	@Override
	public DPath getCurrentPath() {
		return currentPath;
	}
	
	@Override
	public void setCurrentPath(DPath p) {
		if(store.exists(p)) {
			currentPath = p;
		} else {
			err("No such node: " + p.getFullPath());
		}
	}
	
	@Override
	public void addConnectorListener(ConnectorListener l) {
		listeners.add(l);
	}
	
	@Override
	public void removeConnectorListener(ConnectorListener l) {
		listeners.remove(l);
	}
	
	
	// --- PRIVATE METHODS --- //
	
	private void handleImport(CommandLine cl) {
		InputStream in = null;
		try {
			in = getInput(cl);
			Preferences.importPreferences(in);
		} catch(MalformedURLException e) {
			err("Malformed URL: " + cl.getOptionValue("u"));
			log.error("Failed to create URL", e);
		} catch(IOException e) {
			err("Failed to read input: " + e.getMessage());
			log.error("Failed to read input", e);
		} catch (InvalidPreferencesFormatException e) {
			err("Invalid format: " + e.getMessage());
			log.error("Invalid format", e);
		} finally {
			safeClose(in);
		}
		// TODO - We need to force exit here, why?
		System.exit(0);
	}

	private InputStream getInput(CommandLine cl) throws MalformedURLException, IOException {
		InputStream in = input.getDefaultInput();
		if(cl.hasOption("f")) {
			in = new FileInputStream(input.newFile(cl.getOptionValue("f")));
		} else if(cl.hasOption("u")) {
			in = input.newURL(cl.getOptionValue("u")).openStream();
		}
		return in;
	}

	private void handleExec(CommandLine cl) {
		List<String> list = splitForExec(cl.getOptionValue("e"));
		for (String line : list) {
			handleCommandLine(line);
		}
		// TODO We need to force exit here, why?
		System.exit(0);
	}
	
	private List<String> splitForExec(String value) {
		List<String> list = new LinkedList<String>();
		String[] split = value.split(";");
		for (String line : split) {
			line = line.trim();
			if(line.length() > 0) {
				list.add(line);
			}
		}
		return list;
	}
	
	private void handleCommandLine(String line) {
		preExec(line);
		reg.exec(line.split(" "));
		postExec(line);
	}
	
	private void postExec(String line) {
		for (ConnectorListener l : listeners) {
			l.postExecute(line);
		}
	}

	private void preExec(String line) {
		for (ConnectorListener l : listeners) {
			l.preExecute(line);
		}
	}

	private void lineStart() {
		output.printOut(currentPath.getFullPath() + "> ", false);
	}

	private boolean checkSetInitPath(CommandLine cl) {
		currentPath = store.getRootDomainNode().getPath();
		if(cl.hasOption("-p")) {
			try {
				DPath next = currentPath.navigate(cl.getOptionValue("p"));
				if(store.get(next) == null) {
					err("No such node: " + cl.getOptionValue("p"));
					forceExit = true;
				} else {
					currentPath = next;
				}
			} catch(Exception e) {
				err("Illegal path: " + cl.getOptionValue("p"));
				forceExit = true;
			}
		}
		return !forceExit;
	}
}
